package cn.highset.redis.service;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
 * sorted-set 操作类
 * 注意：由于range方法返回的是set，所以在分页查询时，要用withScore 方法，然后根据score 自己 组装List 排序
 *
 * @author gg
 * @version RedisSortedSetService.java, v 0.1 2019-04-11 11:02 gg
 */
public class RedisSortedSetService extends RedisBaseService {

    private RedisTemplate<String, Object> redisTemplate;

    public RedisSortedSetService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }


    /**
     * 对应命令 zAdd
     * 将一个 member 元素及其 score 值加入到有序集 key 当中。
     * 如果某个 member 已经是有序集的成员，那么更新这个 member 的 score 值，并通过重新插入这个 member 元素，来保证该 member 在正确的位置上。
     * score 值可以是整数值或双精度浮点数。
     * 如果 key 不存在，则创建一个空的有序集并执行 zAdd 操作。
     * 当 key 存在但不是有序集类型时，返回一个错误
     *
     * @param key   must not be {@literal null}.
     * @param score the score.
     * @param value the value.
     * @return Boolean
     * @see <a href="http://redis.io/commands/zadd">Redis Documentation: ZADD</a>
     */
    public Boolean zAdd(final String key, Object value, double score) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.add(key, value, score);
    }

    /**
     * 对应命令 zAdd
     * 将一个 或多个 member 元素及其 score 值加入到有序集 key 当中。
     * 如果某个 member 已经是有序集的成员，那么更新这个 member 的 score 值，并通过重新插入这个 member 元素，来保证该 member 在正确的位置上。
     * score 值可以是整数值或双精度浮点数。
     * 如果 key 不存在，则创建一个空的有序集并执行 zAdd 操作。
     * 当 key 存在但不是有序集类型时，返回一个错误
     *
     * @param key must not be {@literal null}.
     * @param map key - 元素 value - 分数
     * @return 被成功添加的新成员的数量，不包括那些被更新的、已经存在的成员
     * @see <a href="http://redis.io/commands/zadd">Redis Documentation: ZADD</a>
     */
    public Long zAdd(final String key, Map<Object, Double> map) {
        checkKey(key);
        Set<ZSetOperations.TypedTuple<Object>> tuples = Sets.newHashSet();
        map.forEach((mapKey, value) -> {
            DefaultTypedTuple<Object> defaultTypedTuple = new DefaultTypedTuple<>(mapKey, value);
            tuples.add(defaultTypedTuple);
        });
        if (CollectionUtils.isEmpty(tuples)) {
            throw new IllegalArgumentException("元素map 不能为空");
        }
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.add(key, tuples);
    }

    /**
     * Remove {@code values} from sorted set. Return number of removed elements.
     *
     * @param key    must not be {@literal null}.
     * @param values must not be {@literal null}.
     * @return 被成功移除的成员的数量，不包括被忽略的成员
     * @see <a href="http://redis.io/commands/zrem">Redis Documentation: ZREM</a>
     */
    public Long remove(final String key, Object... values) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.remove(key, values);
    }

    /**
     * 对应命令 zIncrBy
     * 为有序集 key 的成员 member 的 score 值加上增量 increment 。
     * 可以通过传递一个负数值 increment ，让 score 减去相应的值，比如 ZINCRBY key -5 member ，就是让 member 的 score 值减去 5 。
     * 当 key 不存在，或 member 不是 key 的成员时， ZINCRBY key increment member 等同于 ZADD key increment member 。
     * 当 key 不是有序集类型时，返回一个错误。
     * <p>
     * score 值可以是整数值或双精度浮点数
     *
     * @param key   must not be {@literal null}.
     * @param delta increment
     * @param value the value.
     * @return member 成员的新 score 值，以字符串形式表示
     * @see <a href="http://redis.io/commands/zincrby">Redis Documentation: ZINCRBY</a>
     */
    public Double zIncrBy(final String key, Object value, double delta) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.incrementScore(key, value, delta);
    }

    /**
     * 对应命令 zRank
     * 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
     * 排名以 0 为底，也就是说， score 值最小的成员排名为 0 。
     *
     * @param key   must not be {@literal null}.
     * @param value the value.
     * @return 如果 member 是有序集 key 的成员，返回 member 的排名。 如果 member 不是有序集 key 的成员，返回 nulls
     * @see <a href="http://redis.io/commands/zrank">Redis Documentation: ZRANString</a>
     */
    public Long zRank(final String key, Object value) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.rank(key, value);
    }

    /**
     * 对应 zRevRank 命令
     * 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。
     * 排名以 0 为底，也就是说， score 值最大的成员排名为 0 。
     *
     * @param key   must not be {@literal null}.
     * @param value the value.
     * @return 如果 member 是有序集 key 的成员，返回 member 的排名。 如果 member 不是有序集 key 的成员，返回 null
     * @see <a href="http://redis.io/commands/zrevrank">Redis Documentation: ZREObjectRANString</a>
     */
    public Long zRevRank(final String key, Object value) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.reverseRank(key, value);
    }

    /**
     * 对应命令 zRange
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递增(从小到大)来排序。
     * 具有相同 score 值的成员按字典序(lexicographical order )来排列
     * 下标参数 start 和 stop 都以 0 为底，也就是说，以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推。
     * 你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推
     * <p>
     * 超出范围的下标并不会引起错误。 比如说，当 start 的值比有序集的最大下标还要大，或是 start > stop 时，
     * zRange 命令只是简单地返回一个空列表。 另一方面，假如 stop 参数的值比有序集的最大下标还要大，那么 Redis 将 stop 当作最大下标来处理
     *
     * @param key   must not be {@literal null}.
     * @param start 开始
     * @param end   结束
     * @return 指定区间内，有序集成员的列表
     * @see <a href="http://redis.io/commands/zrange">Redis Documentation: ZRANGE</a>
     */
    public Set<Object> zRange(final String key, long start, long end) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.range(key, start, end);
    }

    /**
     * 对应命令 zRange
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递增(从小到大)来排序。
     * 具有相同 score 值的成员按字典序(lexicographical order )来排列
     * 下标参数 start 和 stop 都以 0 为底，也就是说，以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推。
     * 你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推
     * <p>
     * 超出范围的下标并不会引起错误。 比如说，当 start 的值比有序集的最大下标还要大，或是 start > stop 时，
     * zRange 命令只是简单地返回一个空列表。 另一方面，假如 stop 参数的值比有序集的最大下标还要大，那么 Redis 将 stop 当作最大下标来处理
     *
     * @param key   must not be {@literal null}.
     * @param start 开始
     * @param end   结束
     * @return 指定区间内，带有 score 值(可选)的有序集成员的列表
     * @see <a href="http://redis.io/commands/zrange">Redis Documentation: ZRANGE</a>
     */
    public Map<Object, Double> rangeWithScores(final String key, long start, long end) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> set = zSetOperations.rangeWithScores(key, start, end);
        Map<Object, Double> map = Maps.newHashMap();
        set.forEach(e ->
                map.put(e.getValue(), e.getScore())
        );
        return map;
    }

    /**
     * 对应命令 zRangeByScore
     * 返回有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列
     * 具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的，不需要额外的计算
     * <p>
     * min 和 max 可以是 -inf 和 +inf ，这样一来，你就可以在不知道有序集的最低和最高 score 值的情况下，使用 zRangeByScore 这类命令。
     * 默认情况下，区间的取值使用闭区间 (小于等于或大于等于)，你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)
     *
     * @param key must not be {@literal null}.
     * @param min 最小分数
     * @param max 最大分数
     * @return 指定区间内，有序集成员的列表
     * @see <a href="http://redis.io/commands/zrangebyscore">Redis Documentation: ZRANGEBYSCORE</a>
     */
    public Set<Object> rangeByScore(final String key, double min, double max) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.rangeByScore(key, min, max);
    }

    /**
     * 对应命令 zRangeByScore
     * 返回有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列
     * 具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的，不需要额外的计算
     * <p>
     * min 和 max 可以是 -inf 和 +inf ，这样一来，你就可以在不知道有序集的最低和最高 score 值的情况下，使用 zRangeByScore 这类命令。
     * 默认情况下，区间的取值使用闭区间 (小于等于或大于等于)，你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)
     *
     * @param key must not be {@literal null}.
     * @param min 最小分数
     * @param max 最大分数
     * @return 指定区间内，带有分数有序集成员的列表
     * @see <a href="http://redis.io/commands/zrangebyscore">Redis Documentation: ZRANGEBYSCORE</a>
     */
    public Map<Object, Double> rangeByScoreWithScores(final String key, double min, double max) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = zSetOperations.rangeByScoreWithScores(key, min, max);
        Map<Object, Double> map = Maps.newHashMap();
        typedTuples.forEach(e -> {
            map.put(e.getValue(), e.getScore());
        });
        return map;
    }

    /**
     * 对应命令 zRangeByScore
     * 返回有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列
     * 具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的，不需要额外的计算
     * min 和 max 可以是 -inf 和 +inf ，这样一来，你就可以在不知道有序集的最低和最高 score 值的情况下，使用 zRangeByScore 这类命令。
     * 默认情况下，区间的取值使用闭区间 (小于等于或大于等于)，你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)
     * 可选的 LIMIT 参数指定返回结果的数量及区间(就像SQL中的 SELECT LIMIT offset, count )，
     * 注意当 offset 很大时，定位 offset 的操作可能需要遍历整个有序集，此过程最坏复杂度为 O(N) 时间
     *
     * @param key    must not be {@literal null}.
     * @param min    最小分数
     * @param max    最大分数
     * @param offset 偏移量
     * @param count  LIMIT
     * @return 指定区间内，有序集成员的列表
     * @see <a href="http://redis.io/commands/zrangebyscore">Redis Documentation: ZRANGEBYSCORE</a>
     */
    public Set<Object> rangeByScore(final String key, double min, double max, long offset, long count) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.rangeByScore(key, min, max, offset, count);
    }

    /**
     * 对应命令 zRangeByScore
     *
     * @param key    key
     * @param min    最小分数
     * @param max    最大分数
     * @param offset 偏移量
     * @param count  LIMIT
     * @return 指定区间内，有序集成员的列表 带有分数
     * @see <a href="http://redis.io/commands/zrangebyscore">Redis Documentation: ZRANGEBYSCORE</a>
     */
    public Map<Object, Double> rangeByScoreWithScores(final String key, double min, double max, long offset, long count) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = zSetOperations.rangeByScoreWithScores(key, min, max, offset, count);
        Map<Object, Double> map = Maps.newHashMap();
        typedTuples.forEach(e -> {
            map.put(e.getValue(), e.getScore());
        });
        return map;
    }

    /**
     * 对应命令 zRevRange
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列
     *
     * @param key   must not be {@literal null}.
     * @param start 开始
     * @param end   结束
     * @return 指定区间内，有序集成员的列表
     * @see <a href="http://redis.io/commands/zrevrange">Redis Documentation: ZREObjectRANGE</a>
     */
    public Set<Object> reverseRange(final String key, long start, long end) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.reverseRange(key, start, end);
    }

    /**
     * 对应命令 zRevRange
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列
     *
     * @param key   must not be {@literal null}.
     * @param start 开始
     * @param end   结束
     * @return 指定区间内，有序集成员的列表(带积分)
     * @see <a href="http://redis.io/commands/zrevrange">Redis Documentation: ZREObjectRANGE</a>
     */
    public Map<Object, Double> reverseRangeWithScores(final String key, long start, long end) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = zSetOperations.reverseRangeWithScores(key, start, end);
        Map<Object, Double> map = Maps.newHashMap();
        typedTuples.forEach(e -> {
            map.put(e.getValue(), e.getScore());
        });
        return map;
    }

    /**
     * 对应命令 zRevRange
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列
     *
     * @param key must not be {@literal null}.
     * @param min 最小分数
     * @param max 最大分数
     * @return 指定区间内，有序集成员的列表
     * @see <a href="http://redis.io/commands/zrevrange">Redis Documentation: ZREObjectRANGE</a>
     */
    public Set<Object> reverseRangeByScore(final String key, double min, double max) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.reverseRangeByScore(key, min, max);
    }

    /**
     * 对应命令 zRevRange
     * 返回有序集 key 中，指定区间内的成员。
     * 其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列
     *
     * @param key must not be {@literal null}.
     * @param min 最小分数
     * @param max 最大分数
     * @return 指定区间内，有序集成员的列表
     * @see <a href="http://redis.io/commands/zrevrangebyscore">Redis Documentation: ZREObjectRANGEBYSCORE</a>
     */
    public Map<Object, Double> reverseRangeByScoreWithScores(final String key, double min, double max) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = zSetOperations.reverseRangeByScoreWithScores(key, min, max);
        Map<Object, Double> map = Maps.newHashMap();
        typedTuples.forEach(e ->
                map.put(e.getValue(), e.getScore())
        );
        return map;
    }

    /**
     * 对应命令 zRevRangeByScore
     *
     * @param key    must not be {@literal null}.
     * @param min    最小分数
     * @param max    最大分数
     * @param offset 偏移量
     * @param count  LIMIT
     * @return 指定区间内，有序集成员的列表
     * @see <a href="http://redis.io/commands/zrevrangebyscore">Redis Documentation: ZREObjectRANGEBYSCORE</a>
     */
    public Set<Object> reverseRangeByScore(final String key, double min, double max, long offset, long count) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.reverseRangeByScore(key, min, max, offset, count);
    }

    /**
     * 对应命令 zRevRangeByScore
     *
     * @param key    must not be {@literal null}.
     * @param min    最小分数
     * @param max    最大分数
     * @param offset 偏移量
     * @param count  LIMIT
     * @return 指定区间内，有序集成员的列表(带有分数)
     * @see <a href="http://redis.io/commands/zrevrangebyscore">Redis Documentation: ZREObjectRANGEBYSCORE</a>
     */
    public Map<Object, Double> reverseRangeByScoreWithScores(final String key, double min, double max, long offset, long count) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = zSetOperations.reverseRangeByScoreWithScores(key, min, max, offset, count);
        Map<Object, Double> map = Maps.newHashMap();
        typedTuples.forEach(e ->
                map.put(e.getValue(), e.getScore())
        );
        return map;
    }

    /**
     * 对应命令 zCount
     * 返回有序集 key 中， score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量
     *
     * @param key must not be {@literal null}.
     * @param min 最小分数
     * @param max 最大分数
     * @return score 值在 min 和 max 之间的成员的数量
     * @see <a href="http://redis.io/commands/zcount">Redis Documentation: ZCOUNT</a>
     */
    public Long zCount(final String key, double min, double max) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.count(key, min, max);
    }

    /**
     * 对应命令 zCard
     * 返回有序集 key 的基数
     *
     * @param key key
     * @return 当 key 存在且是有序集类型时，返回有序集的基数。 当 key 不存在时，返回 0
     * @see <a href="http://redis.io/commands/zcard">Redis Documentation: ZCARD</a>
     */
    public Long zCard(final String key) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.size(key);
    }

    /**
     * 对应命令 zScore
     * 返回有序集 key 中，成员 member 的 score 值。
     * 如果 member 元素不是有序集 key 的成员，或 key 不存在，返回 null
     *
     * @param key   must not be {@literal null}.
     * @param value the value.
     * @return member 成员的 score 值
     * @see <a href="http://redis.io/commands/zscore">Redis Documentation: ZSCORE</a>
     */
    public Double zScore(final String key, Object value) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.score(key, value);
    }

    /**
     * 对应命令 zRemRangeByRank
     * 移除有序集 key 中，指定排名(rank)区间内的所有成员
     * 区间分别以下标参数 start 和 stop 指出，包含 start 和 stop 在内。
     * 下标参数 start 和 stop 都以 0 为底，也就是说，以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推。
     * 你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推
     *
     * @param key   must not be {@literal null}.
     * @param start 开始
     * @param end   结束
     * @return 被移除成员的数量
     * @see <a href="http://redis.io/commands/zremrangebyrank">Redis Documentation: ZREMRANGEBYRANString</a>
     */
    public Long removeRange(final String key, long start, long end) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.removeRange(key, start, end);
    }

    /**
     * 对应命令 zRemRangeByScore
     * 移除有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员
     *
     * @param key must not be {@literal null}.
     * @param min 最小分数
     * @param max 最大分数
     * @return 被移除成员的数量
     * @see <a href="http://redis.io/commands/zremrangebyscore">Redis Documentation: ZREMRANGEBYSCORE</a>
     */
    public Long removeRangeByScore(final String key, double min, double max) {
        checkKey(key);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.removeRangeByScore(key, min, max);
    }

    /**
     * 对应命令 zUnionStore
     * 计算给定的一个或多个有序集的并集，并将该并集(结果集)储存到 destination
     * 默认情况下，结果集中某个成员的 score 值是所有给定集下该成员 score 值之 和
     *
     * @param key         must not be {@literal null}.
     * @param otherKey    must not be {@literal null}.
     * @param destination must not be {@literal null}.
     * @return 保存到 destination 的结果集的基数
     * @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
     */
    public Long unionAndStore(final String key, final String otherKey, final String destination) {
        ZSetOperations<String, Object> zSetOperations = checkKeyOtherKeyDestKey(key, otherKey, destination);
        return zSetOperations.unionAndStore(key, otherKey, destination);
    }

    /**
     * 对应命令 zUnionStore
     * 计算给定的一个或多个有序集的并集，并将该并集(结果集)储存到 destination
     * 默认情况下，结果集中某个成员的 score 值是所有给定集下该成员 score 值之 和
     *
     * @param key             must not be {@literal null}.
     * @param otherStringKeys must not be {@literal null}.
     * @param destination     must not be {@literal null}.
     * @return 保存到 destination 的结果集的基数
     * @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
     */
    public Long unionAndStore(final String key, Collection<String> otherStringKeys, final String destination) {
        ZSetOperations<String, Object> zSetOperations = checkKeyDestKeyCollection(key, otherStringKeys, destination);
        return zSetOperations.unionAndStore(key, otherStringKeys, destination);
    }

    private ZSetOperations<String, Object> checkKeyDestKeyCollection(String key, Collection<String> otherStringKeys, String destination) {
        checkKey(key);
        checkDestKey(destination);
        if (CollectionUtils.isEmpty(otherStringKeys)) {
            throw new IllegalArgumentException("key 集合不能为空");
        }
        return redisTemplate.opsForZSet();
    }

    /**
     * 对应命令 zInterStore
     * 计算给定的一个或多个有序集的交集，并将该交集(结果集)储存到 destination 。
     * 默认情况下，结果集中某个成员的 score 值是所有给定集下该成员 score 值之和
     *
     * @param key            must not be {@literal null}.
     * @param otherStringKey must not be {@literal null}.
     * @param destination    must not be {@literal null}.
     * @return 保存到 destination 的结果集的基数
     * @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
     */
    public Long intersectAndStore(final String key, final String otherStringKey, final String destination) {
        ZSetOperations<String, Object> zSetOperations = checkKeyOtherKeyDestKey(key, otherStringKey, destination);
        return zSetOperations.unionAndStore(key, otherStringKey, destination);
    }

    private ZSetOperations<String, Object> checkKeyOtherKeyDestKey(String key, String otherStringKey, String destination) {
        checkKey(key);
        checkOtherKey(otherStringKey);
        checkDestKey(destination);
        return redisTemplate.opsForZSet();
    }

    /**
     * 对应命令 zInterStore
     * 计算给定的一个或多个有序集的交集，并将该交集(结果集)储存到 destination 。
     * 默认情况下，结果集中某个成员的 score 值是所有给定集下该成员 score 值之和
     *
     * @param key             must not be {@literal null}.
     * @param otherStringKeys must not be {@literal null}.
     * @param destination     must not be {@literal null}.
     * @return 保存到 destination 的结果集的基数
     * @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
     */
    public Long intersectAndStore(final String key, Collection<String> otherStringKeys, final String destination) {
        ZSetOperations<String, Object> zSetOperations = checkKeyDestKeyCollection(key, otherStringKeys, destination);
        return zSetOperations.intersectAndStore(key, otherStringKeys, destination);
    }

    /**
     * 对应命令 zScan
     *
     * @param key     key
     * @param pattern 规则
     * @param limit   数量限制
     * @return Cursor
     * @see <a href="http://redis.io/commands/zscan">Redis Documentation: ZSCAN</a>
     * @since 1.4
     */
    public Cursor<ZSetOperations.TypedTuple<Object>> zScan(final String key, final String pattern, long limit) {
        checkKey(key);
        checkPattern(pattern);
        ScanOptions options = ScanOptions.scanOptions().match(pattern).count(limit).build();
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.scan(key, options);
    }

    /**
     * 对应命令 zRangeByLex
     * 通过字典区间返回有序集合的成员
     * 当有序集合的所有成员都具有相同的分值时， 有序集合的元素会根据成员的字典序（lexicographical ordering）来进行排序，
     * 而这个命令则可以返回给定的有序集合键 key 中， 值介于 min 和 max 之间的成员。
     * 如果有序集合里面的成员带有不同的分值， 那么命令返回的结果是未指定的（unspecified）。
     * 命令会使用 C 语言的 memcmp() 函数， 对集合中的每个成员进行逐个字节的对比（byte-by-byte compare），
     * 并按照从低到高的顺序， 返回排序后的集合成员。 如果两个字符串有一部分内容是相同的话， 那么命令会认为较长的字符串比较短的字符串要大。
     *
     * @param key      must not be {@literal null}.
     * @param minEntry must not be {@literal null}.
     * @param maxEntry must not be {@literal null}.
     * @return 数组回复：一个列表，列表里面包含了有序集合在指定范围内的成员
     * @see <a href="http://redis.io/commands/zrangebylex">Redis Documentation: ZRANGEBYLEX</a>
     * @since 1.7
     */
    public Set<Object> rangeByLex(final String key, Map.Entry<Object, Boolean> minEntry, Map.Entry<Object, Boolean> maxEntry) {
        checkKey(key);
        RedisZSetCommands.Range range = getRange(minEntry, maxEntry);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        return zSetOperations.rangeByLex(key, range);
    }

    /**
     * 对应命令 zRangeByLex
     * 通过字典区间返回有序集合的成员
     * 当有序集合的所有成员都具有相同的分值时， 有序集合的元素会根据成员的字典序（lexicographical ordering）来进行排序，
     * 而这个命令则可以返回给定的有序集合键 key 中， 值介于 min 和 max 之间的成员。
     * 如果有序集合里面的成员带有不同的分值， 那么命令返回的结果是未指定的（unspecified）。
     * 命令会使用 C 语言的 memcmp() 函数， 对集合中的每个成员进行逐个字节的对比（byte-by-byte compare），
     * 并按照从低到高的顺序， 返回排序后的集合成员。 如果两个字符串有一部分内容是相同的话， 那么命令会认为较长的字符串比较短的字符串要大。
     * 可选的 LIMIT offset count 参数用于获取指定范围内的匹配元素 （就像 SQL 中的 SELECT LIMIT offset count 语句）。
     * 需要注意的一点是， 如果 offset 参数的值非常大的话， 那么命令在返回结果之前， 需要先遍历至 offset 所指定的位置， 这个操作会为命令加上最多 O(N) 复杂度。
     *
     * @param key      must not be {@literal null}
     * @param minEntry must not be {@literal null}.
     * @param maxEntry must not be {@literal null}.
     * @param offSet   偏移量
     * @param count    数量
     * @return 数组回复：一个列表，列表里面包含了有序集合在指定范围内的成员
     * @see <a href="http://redis.io/commands/zrangebylex">Redis Documentation: ZRANGEBYLEX</a>
     * @since 1.7
     */
    public Set<Object> rangeByLex(final String key, Map.Entry<Object, Boolean> minEntry, Map.Entry<Object, Boolean> maxEntry, int offSet, int count) {
        checkKey(key);
        RedisZSetCommands.Range range = getRange(minEntry, maxEntry);
        ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
        RedisZSetCommands.Limit limit = RedisZSetCommands.Limit.limit();
        limit.offset(offSet).count(count);
        return zSetOperations.rangeByLex(key, range, limit);
    }

    private RedisZSetCommands.Range getRange(Map.Entry<Object, Boolean> minEntry, Map.Entry<Object, Boolean> maxEntry) {
        Boolean minFlag = minEntry.getValue();
        Boolean maxFlag = maxEntry.getValue();
        RedisZSetCommands.Range range = RedisZSetCommands.Range.range();
        range = minFlag ? range.gte(minEntry.getKey()) : range.gt(minEntry.getKey());
        range = maxFlag ? range.lte(maxEntry.getKey()) : range.lt(maxEntry.getKey());
        return range;
    }
}